
// 与Array.reduce相比，fun函数的参数多出一个parentItem
export type ReduceFun<E, R> = (initialValue: R, currentValue: E, index?: number, arr?: E[], parentItem?: E) => R

/**
 * 
 * @param array the array to reduce
 * @param func reduce function
 * @param initialValue initialValue
 * @param childrenProp children prop
 * @param parentItem parent item
 * @returns result
 */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function reduceRecursively<E extends Record<string, any>, R, P extends keyof E = 'children'>(array: E[], func: ReduceFun<E, R>, initialValue: R, childrenProp: P = "children" as P, parentItem?: E): R {
  for (let index = 0; index < array.length; index++) {
    const currentValue = array[index];
    initialValue = func(initialValue, currentValue, index, array, parentItem)
    const children = currentValue[childrenProp];
    if (Array.isArray(children)) {
      initialValue = reduceRecursively(children, func, initialValue, childrenProp, currentValue);
    }
  }
  return initialValue
}


export type FindFun<E, V = undefined> = (this: V, currentValue: E, index?: number, arr?: E[], parentItem?: E) => boolean | undefined

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function findRecursively<E extends Record<string, any>, V = undefined, P extends keyof E = 'children'>(array: E[], func: FindFun<E, V>, thisValue: V = undefined as V, childrenProp: P = "children" as P, parentItem?: E): E | undefined {
  for (let index = 0; index < array.length; index++) {
    const currentValue = array[index];
    const found = func.apply(thisValue, [currentValue, index, array, parentItem]);
    if (found) {
      return currentValue;
    } else {
      const children = currentValue[childrenProp];
      if (Array.isArray(children)) {
        const target: E | undefined = findRecursively(children, func, thisValue, childrenProp, currentValue);
        if (target) {
          return target
        }
      }
    }
  }
}


// 与Array.forEach相比，fun函数的参数多出一个parentItem
export type ForEachFun<E, V = undefined> = (this: V, currentValue: E, index: number, arr?: E[], parentItem?: E) => void



// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function forEachRecursively<E extends Record<string, any>, V = undefined, P extends keyof E = 'children'>(array: E[], func: ForEachFun<E, V>, thisValue: V = undefined as V, childrenFirst?: boolean, childrenProp: P = "children" as P, parentItem?: E) {
  for (let index = 0; index < array.length; index++) {
    const currentValue = array[index];
    if (!childrenFirst)
      func.apply(thisValue, [currentValue, index, array, parentItem]);
    const children = currentValue[childrenProp];
    if (Array.isArray(children)) {
      forEachRecursively(children, func, thisValue, childrenFirst, childrenProp, currentValue);
    }
    if (childrenFirst)
      func.apply(thisValue, [currentValue, index, array, parentItem]);
  }
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export async function asyncForEachRecursively<E extends Record<string, any>, V = undefined, P extends keyof E = 'children'>(array: E[], func: ForEachFun<E, V>, thisValue: V = undefined as V, childrenFirst?: boolean, childrenProp: P = "children" as P, parentItem?: E) {
  for (let index = 0; index < array.length; index++) {
    const currentValue = array[index];
    if (!childrenFirst)
      await func.apply(thisValue, [currentValue, index, array, parentItem]);
    const children = currentValue[childrenProp];
    if (Array.isArray(children)) {
      await asyncForEachRecursively(children, func, thisValue, childrenFirst, childrenProp, currentValue);
    }
    if (childrenFirst)
      await func.apply(thisValue, [currentValue, index, array, parentItem]);
  }
}

  

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type ParentItem<E extends Record<string, any>, R extends Record<string, any>, P1 extends keyof E = 'children', P2 extends keyof R = 'children'> = Omit<R, P2> & Pick<E, P1>


/**
 * parentItem can be E or R. when childrenFirst is false, parentItem's type is R or undefined
 */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type MapFun<E extends Record<string, any>, R extends Record<string, any>, V, P1 extends keyof E = 'children', P2 extends keyof R = 'children'> = (this: V, currentValue: E, index?: number, arr?: E[], parentItem?: E | ParentItem<E, R, P1, P2>) => R



/**
 * 
 * @param array 
 * @param func 
 * @param thisValue 
 * @param childrenFirst process children first or not, default is false
 * @param childrenProp 
 * @param parentItem 
 * @returns map result type.
 */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function mapRecursively<E extends Record<string, any>, R extends Record<string, any>, V = undefined, P1 extends keyof E = 'children', P2 extends keyof R = 'children'>(array: E[], func: MapFun<E, R, V, P1, P2>, thisValue = undefined as V, childrenFirst?: boolean, childrenProp: P1 | [P1, P2] = ('children' as P1), parentItem?: E | ParentItem<E, R, P1, P2>): R[] {
  const resultArray: R[] = []
  
  let originChildrenProp, targetChildrenProp
  if (typeof childrenProp == "string") {
    originChildrenProp = childrenProp
    targetChildrenProp = childrenProp
  } else if (Array.isArray(childrenProp) && childrenProp.length == 2) {
    originChildrenProp = childrenProp[0] || 'children'
    targetChildrenProp = childrenProp[1] || 'children'
  } else {
    originChildrenProp = 'children'
    targetChildrenProp = 'children'
  }

  for (let index = 0; index < array.length; index++) {
    const currentValue = array[index];
    //node prior to children
    if (!childrenFirst) {
      const originChildren = currentValue[originChildrenProp as string]
      const hasOriginChildren = Array.isArray(originChildren) && originChildren.length > 0
      const resultItem = func.apply(thisValue, [currentValue, index, array, parentItem]);
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      const children = resultItem[originChildrenProp as string] as Record<string, any>;
      if (Array.isArray(children)) {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        (resultItem as Record<string, any>)[targetChildrenProp as string] = mapRecursively(
          children,
          func, thisValue, childrenFirst, childrenProp, resultItem as ParentItem<E, R, P1, P2>
        );
      } else if (hasOriginChildren) {
        console.warn("When subitems are not prioritized, the mapping function return value should contain subitems. If that's what you mean, ignore this.")
      }
      resultArray.push(resultItem)
    } else {
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      const children = currentValue[originChildrenProp as string] as Record<string, any>;
      if (Array.isArray(children)) {
        const processedChildren = mapRecursively(
          children,
          func, thisValue, childrenFirst, childrenProp, currentValue
        );
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        (currentValue as Record<string, any>)[originChildrenProp as string] = processedChildren
      }

      const resultItem = func.apply(thisValue, [currentValue, index, array, parentItem]);
      resultArray.push(resultItem)
    }
  }


  return resultArray
}


export type FilterFun<E, V = undefined> = (this: V, currentValue: E, index?: number, arr?: E[], parentItem?: E) => boolean | undefined


// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function filterRecursively<E extends Record<string, any>, V = undefined, P extends keyof E = 'children'>(array: E[], func: FilterFun<E, V>, thisValue = undefined as V, childrenFirst?: boolean, childrenProp: P = "children" as P, parentItem?: E): E[] {
  const filteredArray: E[] = []
  for (let index = 0; index < array.length; index++) {
    const currentValue = array[index];
    if (childrenFirst) {
      const children = currentValue[childrenProp];
      if (Array.isArray(children)) {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        (currentValue as Record<string, any>)[childrenProp as string] = filterRecursively(children, func,
          thisValue,
          childrenFirst,
          childrenProp,
          currentValue);
      }
    }
    const filterStatus = func.apply(thisValue, [currentValue as E, index, array, parentItem]);
    if (filterStatus) {
      if (!childrenFirst) {
        const children = currentValue[childrenProp];
        if (Array.isArray(children)) {
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
          (currentValue as Record<string, any>)[childrenProp as string] = filterRecursively(children, func,
            thisValue,
            childrenFirst,
            childrenProp,
            currentValue
          );
        }
      }
      filteredArray.push(currentValue)
    }
  }
  return filteredArray
}

